

using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using Microsoft.VisualBasic;
using UMC.Data;
using UMC.Net;
using UMC.SshNet.Messages.Connection;

namespace UMC.ITME;

class HttpsMimeSocket : HttpMime
{

    public static UMC.Data.Caches.Cache ClientCache;
    static byte[] challengeData;
    static HttpsMimeSocket()
    {
        ClientCache = new Data.Caches.Cache("Time", 1200, new ClientInfo(), "IP", "Finger");

        using (System.IO.Stream stream = typeof(WebServlet).Assembly
                                    .GetManifestResourceStream($"UMC.ITME.Resources.auth.html"))
        {
            challengeData = new byte[stream.Length];
            stream.Read(challengeData, 0, challengeData.Length);
            stream.Close();
        }
    }
    public class ClientInfo : Record
    {
        protected override RecordColumn[] GetColumns()
        {
            var cols = new RecordColumn[6];
            cols[0] = RecordColumn.Column("IP", this.IP);
            cols[1] = RecordColumn.Column("Finger", this.IP);
            cols[2] = RecordColumn.Column("Time", this.Time);
            cols[3] = RecordColumn.Column("Quantity", this.Quantity);
            cols[4] = RecordColumn.Column("Verify", this.Verify);
            cols[5] = RecordColumn.Column("Error", this.Error);
            return cols;
        }
        protected override void SetValue(string name, object obv)
        {

            switch (name)
            {
                case "IP":
                    this.IP = (StringValue)obv;
                    break;
                case "Finger":
                    this.Finger = (StringValue)obv;
                    break;
                case "Time":
                    this.Time = (StringValue)obv;
                    break;
                case "Quantity":
                    this.Quantity = (StringValue)obv;
                    break;
                case "Verify":
                    this.Verify = (StringValue)obv;
                    break;
                case "Error":
                    this.Error = (StringValue)obv;
                    break;
            }
        }
        protected override void GetValues(Action<string, object> action)
        {

            AppendValue(action, "IP", this.IP);
            AppendValue(action, "Finger", this.Finger);
            AppendValue(action, "Time", this.Time);
            AppendValue(action, "Quantity", this.Quantity);
            AppendValue(action, "Verify", this.Verify);
            AppendValue(action, "Error", this.Error);


        }
        public StringValue IP { get; set; } = StringValue.Empty;
        public StringValue Finger { get; set; } = StringValue.Empty;
        public StringValue Time { get; set; } = StringValue.Empty;//CookeTime
        public StringValue Quantity { get; set; } = StringValue.Empty;
        public StringValue Verify { get; set; } = StringValue.Empty;
        public StringValue Error { get; set; } = StringValue.Empty;
    }
    public override void CheckTimeOut()
    {
        if (this.IsWrited)
        {
            this.Stop();
        }
        else if (this.Request is not null)
        {
            this.OutText(504, "Gateway Timeout");
        }
        else
        {
            this.Dispose();
        }
    }
    public TlsSite tlsSite1;
    public override bool IsSSL => true;
    public override object Tag => tlsSite1;// base.Tag;
    public ClientInfo clientInfo;

    public override StringValue Finger => tlsSite1.Finger;
    Socket clientSocket;
    public HttpsMimeSocket(System.IO.Stream stream, Socket socket, TlsSite tlsSite, StringValue iPAddress, Memory<byte> buffer = default)

    {
        this.clientInfo = HttpsMimeSocket.ClientCache.Get(new HttpsMimeSocket.ClientInfo { IP = iPAddress, Finger = tlsSite.Finger });
        if (clientInfo == null)
        {
            clientInfo = new HttpsMimeSocket.ClientInfo
            {
                IP = iPAddress,
                Finger = tlsSite.Finger,
                Quantity = new StringValue(BitConverter.GetBytes(0)),
                Error = new StringValue(BitConverter.GetBytes(0)),
                Verify = new StringValue(BitConverter.GetBytes(false)),
                Time = new StringValue(BitConverter.GetBytes(Utility.TimeSpan()))
            };
            HttpsMimeSocket.ClientCache.Put(clientInfo);
        }
        BitConverter.TryWriteBytes(clientInfo.Quantity.Span, BitConverter.ToInt32(clientInfo.Quantity.Span) + 1);

        this.tlsSite1 = tlsSite;
        this.ActiveTime = UMC.Data.Utility.TimeSpan();
        this._remoteIpAddress = iPAddress;
        // this.clientInfo = clientSocket;

        if (tlsSite.IsSecure || (tlsSite.IsHome && tlsSite.SiteHost?.IsAuthModel == true) || tlsSite.Site?.IsDisabledFingerprint == true)
        {
            IsPass = true;

        }
        this._stream = stream;
        this.clientSocket = socket;

        this.pid = this.GetHashCode();
        HttpMimeServier.httpMimes.TryAdd(pid, this);
        Read(buffer);

    }


    Stream _stream;
    int pid = 0;
    public override int Id => pid;
    StringValue _remoteIpAddress;
    static StringValue _Host = new StringValue("127.0.0.1");
    public override StringValue Host => _Host;
    public override StringValue RemoteIpAddress => _remoteIpAddress;


    public static void Challenge(StringValue host, int statecode, bool isClearCookie, string version, StringValue ipValue, String lang, StringValue fingerprint, String root, NetWriteData writeData)
    {
        var arl = System.Buffers.ArrayPool<byte>.Shared.Rent(600);
        try
        {
            var appId = UMC.Data.WebResource.Instance().Provider["appId"];
            var time = Utility.TimeSpan();
            var sb = new System.Text.StringBuilder();
            WebServlet.AppId.Span.CopyTo(arl);
            BitConverter.TryWriteBytes(arl.AsSpan(16), time);
            ipValue.Span.CopyTo(arl.AsSpan(20));
            int l = ipValue.Length;
            var ver = version.UTF8();
            ver.CopyTo(arl.AsSpan(20 + l));
            var size = 20 + l + ver.Length;
            WebServlet.AppSecret.Span.CopyTo(arl.AsSpan(size));
            var sign = Utility.MD5(arl.AsSpan(0, size + 16));
            sb.Append("https://challenges.apiumc.com/Challenge/");
            sb.Append(appId);
            sb.Append("/");
            sb.Append(time);
            sb.Append("/");
            sb.Append(Utility.ToBase64String(ipValue.Span));
            sb.Append("/");
            sb.Append(version);
            sb.Append("/");
            sb.Append(Utility.ToBase64String(sign));
            sb.Append("/");
            sb.Append(Utility.ToBase64String(host.Span));
            sb.Append("/");
            if (fingerprint.IsEmpty() == false)
            {
                sb.Append(Utility.ToBase64String(fingerprint.Span));
                if (String.IsNullOrEmpty(root) == false)
                {
                    sb.Append("/");
                    sb.Append(root);
                }
            }

            var base64 = Convert.ToBase64String(sb.ToString().UTF8());
            sb.Clear();
            sb.Append(".challenge{background-image:url(\"data:image/svg+xml;base64,");
            sb.Append(base64);
            sb.Append("\")}");


            var rayId = Utility.Hex(BitConverter.GetBytes(Utility.NewId()));

            var writer = new Net.TextWriter(writeData);
            var jsversion = $"1.{Random.Shared.Next(0, 9)}?rayId={rayId}";

            writer.Write($"HTTP/1.1 {statecode} {HttpStatusDescription.Get(statecode)}\r\n");
            writer.Write($"Content-Type: text/html; charset=utf-8\r\n");
            writer.Write($"Expires: 0\r\n");
            writer.Write("Connection: close\r\n");
            writer.Write("Pragma: no-cache\r\n");
            writer.Write($"Content-Security-Policy: default-src 'self' data: 'unsafe-eval' 'unsafe-inline' https://*.apiumc.com;\r\n");
            writer.Write("Cache-Control: no-store\r\n");
            writer.Write($"Content-Length: {sb.Length + challengeData.Length + lang.Length + rayId.Length + jsversion.Length}\r\n");
            if (isClearCookie)
            {
                if (host.Span.EndsWith(WebServlet.MainDomainValue.Span))
                {
                    var cdmn = WebServlet.MainDomain;
                    var cdm = cdmn;

                    if (WebServlet.DomainUnion == '-')
                    {
                        cdm = cdmn.Substring(cdmn.IndexOf('.') + 1);
                    }
                    writer.Write($"Set-Cookie: {HttpsMimeRequest.authKey}=; Expires={DateTime.Now.ToUniversalTime().AddSeconds(-900):r}; HttpOnly; SameSite=Lax; Domain={cdm}; Path=/\r\n");
                }
                else
                {
                    writer.Write($"Set-Cookie: {HttpsMimeRequest.authKey}=; Expires={DateTime.Now.ToUniversalTime().AddSeconds(-900):r}; HttpOnly; SameSite=Lax; Path=/\r\n");

                }
            }
            writer.Write("Server: ITME\r\n\r\n");
            var res = new Tuple<StringValue, String>[]
            {
                    Tuple.Create(new StringValue("\"></script>"), jsversion),
                    Tuple.Create(new StringValue("\"></script>"), lang),
                    Tuple.Create(new StringValue("</style>"), sb.ToString()),
                    Tuple.Create(new StringValue("</code>"), rayId)
            };
            int reIndex = 0;

            var lr = challengeData.AsSpan();
            while (reIndex < res.Length)
            {
                var rayIndex = lr.IndexOf(res[reIndex].Item1.Span);
                if (rayIndex > 0)
                {
                    writer.Write(lr.Slice(0, rayIndex));
                    writer.Write(res[reIndex].Item2);
                    writer.Write(res[reIndex].Item1.Span);
                    lr = lr.Slice(rayIndex + res[reIndex].Item1.Span.Length);
                    reIndex++;
                }
                else
                {
                    break;
                }
            }
            writer.Write(lr);

            writer.Flush();

            writer.Close();

        }
        finally
        {
            System.Buffers.ArrayPool<byte>.Shared.Return(arl);
        }

    }
    public override void Challenge(StringValue host, int statecode, bool isClearCookie, string version)
    {
        String lang;
        if (this.Request.Headers.TryGet(StringValue.AcceptLanguage.Span, out var headValue))
        {
            lang = $"{headValue.Value.Slice(","u8).UTF8()}.js";
        }
        else
        {
            lang = "zh-CN.js";
        }
        Challenge(host, statecode, isClearCookie, version, this.RemoteIpAddress, lang, this.tlsSite1.Fingerprint, this.tlsSite1.Site?.Root ?? String.Empty, this.Send);

        if (this.Request is HttpMimeRequest)
        {
            var h = this.Request as HttpMimeRequest;
            h.netLoger.Write(HttpLogerType.Status, statecode);
            HttpLogerDoWorker.AppendLog(h.netLoger);
        }
        this.Dispose();

    }

    public override void Stop()
    {
        if (this.IsWrited)
        {
            this.channel.Writer.TryComplete();
        }
        else
        {
            this.Dispose();
        }
    }
    Channel<Tuple<long, int, byte[]>> channel;

    // NetShare share;
    bool isStop;
    protected override void Send(byte[] buffer, int offset, int count)
    {
        try
        {
            if (isStop == false)
            {
                _stream.Write(buffer, offset, count);

            }
        }
        catch //(Exception ex)
        {
            isStop = true;
            this.Dispose();
        }
    }
    async void Receive(ChannelReader<Tuple<long, int, byte[]>> reader)
    {
        var buffer = System.Buffers.ArrayPool<byte>.Shared.Rent(400);
        this.ActiveTime = Utility.TimeSpan();
        bool IsError = false;
        while (await reader.WaitToReadAsync())
        {
            var values = reader.ReadAllAsync().GetAsyncEnumerator();
            while (await values.MoveNextAsync())
            {
                var result = values.Current;
                if (result.Item1 == -1)
                {
                    if (result.Item2 > 0)
                    {
                        try
                        {
                            if (isStop == false)
                                await _stream.WriteAsync(result.Item3, 0, result.Item2);
                        }
                        catch
                        {
                            isStop = true;

                            this.Dispose();
                        }

                        waitSize -= result.Item2;
                        System.Buffers.ArrayPool<byte>.Shared.Return(result.Item3);
                    }
                    else if (this._httpLoger != null)
                    {
                        this.ActiveTime = Utility.TimeSpan();
                        this.TimeOut = 10;
                        this._httpLoger.Write(HttpLogerType.Duration, (int)(UMC.Data.Reflection.TimeSpanMilli(DateTime.Now) - _startTime));

                        HttpLogerDoWorker.AppendLog(this._httpLoger);
                        this._httpLoger = null;
                    }
                }
                else
                {

                    using (var view = NetCache.Share.CreateAccessor(result.Item1, result.Item2))
                    {


                        int size = 0;
                        while ((size = view.Read(buffer, 0, buffer.Length)) > 0)
                        {
                            try
                            {
                                if (isStop == false)
                                    await _stream.WriteAsync(buffer, 0, size);
                            }
                            catch
                            {
                                isStop = true;

                                this.Dispose();
                            }
                        }
                    }

                }
                var now = Utility.TimeSpan();
                var value = now - this.ActiveTime;
                this.ActiveTime = now;
                if (value > 4)
                {
                    if ((result.Item2 / value) < (tlsSite1.Site?.ApiSecureNetworkSpeed ?? 100))
                    {
                        var error = BitConverter.ToInt32(this.clientInfo.Quantity.Span);
                        if (error > 5)
                        {
                            if (IsError == false)
                            {
                                IsError = true;
                                if (_httpLoger != null)
                                {
                                    this.isStop = true;
                                    _httpLoger.Warn().Write("Slow read攻击").End();
                                }

                                BitConverter.TryWriteBytes(clientInfo.Error.Span, BitConverter.ToInt32(this.clientInfo.Error.Span) + 1);
                            }
                        }

                    }
                }
            }
        }

        ArrayPool<byte>.Shared.Return(buffer);
        if (this._httpLoger != null)
        {

            this._httpLoger.Write(HttpLogerType.Duration, (int)(UMC.Data.Reflection.TimeSpanMilli(DateTime.Now) - _startTime));

            HttpLogerDoWorker.AppendLog(this._httpLoger);
            this._httpLoger = null;
        }
        IDisposable();
        this.Dispose();
    }
    long _startTime;
    HttpLoger _httpLoger;
    public override void WriteLog(long startTime, HttpLoger loger)
    {
        if (channel?.Writer?.TryWrite(Tuple.Create(-1L, 0, Array.Empty<byte>())) == false)
        {
            this._httpLoger = loger;
            this._startTime = startTime;
        }
        else
        {
            base.WriteLog(startTime, loger);
        }

    }
    int waitSize = 0; bool isNetShare = false;
    public override void Write(byte[] buffer, int offset, int count)
    {
        if (isDispose == false)
        {
            if (channel == null)
            {
                channel = Channel.CreateUnbounded<Tuple<long, int, byte[]>>(new UnboundedChannelOptions()
                {
                    SingleReader = true,
                    SingleWriter = true
                });

                this.Receive(channel.Reader);
            }

            if (isNetShare)
            {

                var position = NetCache.Share.Append(buffer, offset, count);
                channel.Writer.TryWrite(Tuple.Create(position, count, Array.Empty<byte>()));
            }
            else
            {
                waitSize += count;
                var bytes = count > 0 ? ArrayPool<byte>.Shared.Rent(count) : Array.Empty<byte>();
                Array.Copy(buffer, offset, bytes, 0, count);
                channel.Writer.TryWrite(Tuple.Create(-1L, count, bytes));
                if (waitSize > 1000000)
                {
                    isNetShare = true;

                }
            }
        }
    }
    bool isDispose = false;
    public override void Dispose()
    {

        if (isDispose == false)
        {
            isDispose = true;
            if (this.channel == null)
            {
                IDisposable();
            }
            else
            {
                this.channel.Writer.TryComplete();
            }
        }
        HttpMimeServier.httpMimes.TryRemove(pid, out var _);

    }
    void IDisposable()
    {
        BitConverter.TryWriteBytes(clientInfo.Quantity.Span, BitConverter.ToInt32(clientInfo.Quantity.Span) - 1);
        try
        {
            this.clientSocket.Close();
            this.clientSocket.Dispose();
        }
        catch
        {

        }
        try
        {

            _stream.Close();
            _stream.Dispose();


        }
        catch
        {

        }
        try
        {
            if (this.Request is IDisposable)
            {
                ((IDisposable)this.Request).Dispose();
            }
        }
        catch
        {

        }
    }

    async void Read(Memory<byte> memory)
    {

        byte[] data = System.Buffers.ArrayPool<byte>.Shared.Rent(0x600);
        memory.CopyTo(data);
        try
        {
            this.ActiveTime = UMC.Data.Utility.TimeSpan();
            uint reqTime = 0;
            uint lastTime = this.ActiveTime;
            int start = memory.Length, size = data.Length - start;//, start = memory.Length;
            bool IsError = false;
            while ((size = await _stream.ReadAsync(data, start, size)) > 0)
            {
                this.ActiveTime = UMC.Data.Utility.TimeSpan();

                var readTime = this.ActiveTime - lastTime;
                if (this.Request == null || this.Request?.IsMimeFinish == true)
                {
                    readTime = 0;
                    reqTime = this.ActiveTime;
                    var netLoger = new HttpLoger(new ByteChunk());
                    netLoger.Write(HttpLogerType.IP, this._remoteIpAddress);
                    netLoger.Write(HttpLogerType.Fingerprint, tlsSite1.Finger);
                    this.MimeMemory.Reset();
                    this.Request = new HttpsMimeRequest(this, this.MimeMemory, netLoger);
                    int qty = BitConverter.ToInt32(this.clientInfo.Quantity.Span);
                    var ApiSecureConnections = tlsSite1.Site?.ApiSecureConnections ?? 100;
                    if (qty > ApiSecureConnections)
                    {
                        var RemoteIpAddress = this.RemoteIpAddress;
                        int value = HttpMimeServier.httpMimes.Values.Sum(d => d.RemoteIpAddress.Span.SequenceEqual(RemoteIpAddress.Span) ? 1 : 0);
                        if (value > ApiSecureConnections)
                        {
                            netLoger.Warn().Write($"此IP已经连接数:{value}").End();
                        }
                    }

                }
                var req = this.Request;
                size += start;
                var len = req.Receive(data, 0, size);
                if (req.IsHttpFormatError)
                {
                    this.OutText(400);
                    break;
                }

                if (len > 0)
                {
                    Array.Copy(data, size - len, data, 0, len);
                    start = len;
                    size = data.Length - len;
                }
                else
                {
                    start = 0;
                    size = data.Length;
                }

                if (req is HttpMimeRequest)
                {
                    if (req.IsHeaderFinish == false)
                    {
                        if (req.IsReadFrist)
                        {
                            if (req.IsKeyStart && req.Memory.TempSize > 255)
                            {
                                this.OutText(431, "请求Header字段名称太长");
                                break;
                            }
                            if (req.Memory.TempSize > (tlsSite1.Site?.ApiSecureFieldSize ?? 500))
                            {
                                this.OutText(431, "请求Header字段值太长，请清空浏览器的Cookie");
                                break;
                            }
                            if (req.Headers.Count > (tlsSite1.Site?.ApiSecureHeadCount ?? 30))
                            {
                                this.OutText(431, "请求Header条目太多");
                                break;
                            }
                        }
                        else if (req.Memory.TempSize > (tlsSite1.Site?.ApiSecureUrlSize ?? 1500))
                        {
                            this.OutText(414);
                            break;
                        }
                        if (this.ActiveTime - reqTime > (tlsSite1.Site?.ApiSecureHeadTimeOut ?? 10))
                        {
                            var error = BitConverter.ToInt32(this.clientInfo.Quantity.Span);
                            if (error > 5)
                            {
                                this.OutText(431, "请求Header超时,请确认当前环境");
                            }

                            break;
                        }
                    }
                    else if (req.IsMimeFinish == false)
                    {
                        if (readTime > 5)
                        {
                            var error = BitConverter.ToInt32(this.clientInfo.Quantity.Span);
                            if (error > 5)
                            {
                                var speet = size / readTime;
                                if (req.ChunkSize / speet > (tlsSite1.Site?.ApiSecureBodyTimeOut ?? 300) && speet < (tlsSite1.Site?.ApiSecureNetworkSpeed ?? 100))
                                {
                                    if (IsError == false)
                                    {
                                        IsError = true;
                                        BitConverter.TryWriteBytes(clientInfo.Error.Span, BitConverter.ToInt32(this.clientInfo.Error.Span) + 1);
                                        var h = req as HttpMimeRequest;
                                        h.netLoger.Warn().Write("Slow post攻击").End();
                                    }
                                }
                            }
                        }
                    }
                }


                lastTime = UMC.Data.Utility.TimeSpan();

            }
        }
        catch (System.OutOfMemoryException)
        {
            GC.Collect();
            this.Dispose();
        }
        catch// (Exception ex)
        {
            this.Dispose();

        }

    }


}
